package com.newworld.basketballfire.main.business;

import com.newworld.basketballfire.main.api.Const;
import com.newworld.basketballfire.main.api.ErrorCode;
import com.newworld.basketballfire.main.bean.ActivityListParam;
import com.newworld.basketballfire.main.dao.ActivityDao;
import com.newworld.basketballfire.main.dao.DuesDao;
import com.newworld.basketballfire.main.dao.UserDao;
import com.newworld.basketballfire.main.utils.CommonUtils;
import com.newworld.basketballfire.main.utils.InputChecker;
import com.newworld.basketballfire.service.exception.ServiceException;
import com.newworld.basketballfire.service.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by George on 2019/3/31.
 * ActivityBusiness
 */
@Component
public class ActivityBusiness {

    private static Logger logger = LoggerFactory.getLogger(ActivityBusiness.class);

    @Autowired
    private ActivityDao activityDao;

    @Autowired
    private UserDao userDao;

    @Autowired
    private DuesDao duesDao;

    public Activity createActivity(Activity activity) throws ServiceException {
        InputChecker.checkDuesType(activity.getType());
        Map<String, User> usersMap = checkRecordAndGetUsers(activity.getRecords(), activity.getUserNum());

        activity.setId(CommonUtils.getUUID());
        activity.setLastUpdateTime(CommonUtils.getCurrentTime());
        activity.setVersion(Const.DEFAULT_VERSION);
        activity.setDues(CommonUtils.adjustDuesValue(activity.getType(), activity.getDues()));

        List<Dues> duesList = new ArrayList<>();
        List<User> users = new ArrayList<>();
        BigDecimal averageDues = activity.getDues().divide(new BigDecimal(activity.getUserNum()), 2, RoundingMode.UP);
        handleActivityRecord(activity, usersMap, duesList, users, averageDues);
        activityDao.createActivity(activity, users, duesList);
        return activity;
    }

    private void handleActivityRecord(Activity activity, Map<String, User> usersMap, List<Dues> duesList, List<User> users, BigDecimal averageDues) {
        for (ActivityRecord record : activity.getRecords()) {
            BigDecimal userDues = averageDues.multiply(new BigDecimal(record.getUserNum()));
            Dues dues = new Dues();
            dues.setId(CommonUtils.getUUID());
            dues.setType(activity.getType());
            dues.setUserId(record.getUserId());
            dues.setActivityId(activity.getId());
            dues.setDues(userDues);
            dues.setDate(activity.getDate());
            dues.setInfo(Const.DEFAULT_ACTIVITY_DUES_INFO);
            dues.setLastUpdateTime(CommonUtils.getCurrentTime());
            dues.setVersion(Const.DEFAULT_VERSION);

            record.setActivityId(activity.getId());
            record.setDuesId(dues.getId());

            User user = usersMap.get(record.getUserId());
            user.setDues(user.getDues().add(userDues));

            duesList.add(dues);
            users.add(user);
        }
    }

    private Map<String, User> checkRecordAndGetUsers(List<ActivityRecord> records, int userNum) throws ServiceException {
        if (CollectionUtils.isEmpty(records)) {
            logger.error("the activity record is null or empty.");
            throw new ServiceException(HttpStatus.BAD_REQUEST, ErrorCode.INVALID_PARAMS, "the activity record is null or empty.");
        }

        int userNumCount = 0;
        for (ActivityRecord record : records) {
            userNumCount += record.getUserNum();
        }

        if (userNum != userNumCount) {
            logger.error("the user num count of activity record not equal the user num of activity.");
            throw new ServiceException(HttpStatus.BAD_REQUEST, ErrorCode.INVALID_PARAMS, "the user num count of activity record not equal the user num of activity.");
        }

        return getUsersMapByActivityRecords(records);
    }

    private HashMap<String, User> getUsersMapByActivityRecords(List<ActivityRecord> records) throws ServiceException {
        List<User> users = getUsersByActivityRecords(records);
        return getUsersMapByUsersList(users);
    }

    private HashMap<String, User> getUsersMapByUsersList(List<User> users) {
        HashMap<String, User> usersMap = new HashMap<>();
        for (User user : users) {
            usersMap.put(user.getId(), user);
        }
        return usersMap;
    }

    private List<User> getUsersByActivityRecords(List<ActivityRecord> records) throws ServiceException {
        List<String> ids = new ArrayList<>();
        for (ActivityRecord record : records) {
            if (ids.contains(record.getUserId())) {
                logger.error("the user of activity record is repeat.");
                throw new ServiceException(HttpStatus.BAD_REQUEST, ErrorCode.INVALID_PARAMS, "the user of activity record is repeat.");
            }
            ids.add(record.getUserId());
        }

        List<User> users = userDao.getUsersByIds(ids);
        if (CollectionUtils.isEmpty(users) || users.size() != ids.size()) {
            logger.error("the user of activity record is invalid or not exist.");
            throw new ServiceException(HttpStatus.BAD_REQUEST, ErrorCode.INVALID_PARAMS, "the user of activity record is invalid.");
        }
        return users;
    }

    public Activity updateActivity(Activity activity) throws ServiceException {
        Activity oldActivity = activityDao.getActivity(activity.getId());
        if (oldActivity == null || !activity.getId().equals(oldActivity.getId())) {
            logger.error("the activity is invalid.");
            throw new ServiceException(HttpStatus.BAD_REQUEST, ErrorCode.INVALID_PARAMS, "the activity is invalid.");
        }

        InputChecker.checkDuesType(activity.getType());
        Map<String, User> usersMap = checkRecordAndGetUsers(activity.getRecords(), activity.getUserNum());

        activity.setDues(CommonUtils.adjustDuesValue(activity.getType(), activity.getDues()));
        activity.setLastUpdateTime(CommonUtils.getCurrentTime());
        activity.setVersion(oldActivity.getVersion());

        List<Dues> duesList = new ArrayList<>();
        List<User> users = new ArrayList<>();
        BigDecimal averageDues = activity.getDues().divide(new BigDecimal(activity.getUserNum()), 2, RoundingMode.UP);
        handleActivityRecord(activity, usersMap, duesList, users, averageDues);

        Map<String, User> newUsersMap = getUsersMapByUsersList(users);

        List<User> oldUsers = getUsersByActivityRecords(oldActivity.getRecords());
        List<Dues> oldDuesList = getDuesListByActivityRecords(oldActivity.getRecords());
        Map<String, Dues> oldUserId2Dues = oldUserId2DuesMapByDuesList(oldDuesList);
        List<ActivityRecord> oldRecords = oldActivity.getRecords();

        oldUsers.forEach(oldUser -> {
            Dues dues = oldUserId2Dues.get(oldUser.getId());
            if (newUsersMap.containsKey(oldUser.getId())) {
                User user = newUsersMap.get(oldUser.getId());
                user.setDues(user.getDues().subtract(dues.getDues()));
            } else {
                oldUser.setDues(oldUser.getDues().subtract(dues.getDues()));
                users.add(oldUser);
            }
        });

        activityDao.updateActivity(activity, users, duesList, oldDuesList, oldRecords);
        return activity;
    }

    private Map<String, Dues> oldUserId2DuesMapByDuesList(List<Dues> oldDuesList) {
        Map<String, Dues> userId2Dues = new HashMap<>();
        oldDuesList.forEach(obj -> userId2Dues.put(obj.getUserId(), obj));
        return userId2Dues;
    }

    private List<Dues> getDuesListByActivityRecords(List<ActivityRecord> records) throws ServiceException {
        List<String> ids = new ArrayList<>();
        records.forEach(obj -> ids.add(obj.getDuesId()));

        List<Dues> duesList = duesDao.getDuesListByIds(ids);
        if (CollectionUtils.isEmpty(duesList)) {
            logger.error("the dues list is empty.");
            throw new ServiceException(HttpStatus.BAD_REQUEST, ErrorCode.INVALID_PARAMS, "the dues list is empty.");
        }
        return duesList;
    }

    public Activity getActivity(String id) {
        return activityDao.getActivity(id);
    }

    public void deleteActivity(String id) throws ServiceException {
        Activity activity = activityDao.getActivity(id);
        if (null == activity || !activity.getId().equals(id)) {
            logger.error("the activity in not exist.");
            throw new ServiceException(HttpStatus.BAD_REQUEST, ErrorCode.INVALID_PARAMS, "the activity in not exist.");
        }

        List<User> users = new ArrayList<>();
        HashMap<String, User> usersMap = new HashMap<>();
        List<Dues> duesList = getDuesList(activity, usersMap);
        duesList.forEach(dues -> {
            User user = usersMap.get(dues.getUserId());
            user.setDues(user.getDues().subtract(dues.getDues()));
            users.add(user);
        });

        activityDao.deleteActivity(activity, users, duesList);
        logger.warn("delete activity success. id={}.", activity.getId());
    }

    private List<Dues> getDuesList(Activity activity, HashMap<String, User> usersMap) {
        List<String> userIdList = new ArrayList<>();
        List<String> duesIdList = new ArrayList<>();
        activity.getRecords().forEach(obj -> {
            userIdList.add(obj.getUserId());
            duesIdList.add(obj.getDuesId());
        });

        List<User> users = userDao.getUsersByIds(userIdList);
        List<Dues> duesList = duesDao.getDuesListByIds(duesIdList);
        for (User user : users) {
            usersMap.put(user.getId(), user);
        }
        return duesList;
    }

    public ListResult<Activity> getActivityList(ActivityListParam activityListParam) {
        int totalCnt = activityDao.getActivityListTotalCnt(activityListParam);
        List<Activity> activities;
        if (totalCnt > 0) {
            activities = activityDao.getActivityList(activityListParam);
        } else {
            activities = new ArrayList<>();
        }

        ListResult<Activity> listResult = new ListResult<>();
        listResult.setData(activities);
        listResult.setTotalCnt(totalCnt);
        listResult.setPageSize(activityListParam.getPageSize());
        listResult.setPageNo(activityListParam.getPageNo());
        return listResult;
    }
}
